﻿namespace DatabaseGroup.SSIS.SourceAdapters.Parser
{
    /// <summary>
    /// Matches a string token to incoming characters, 
    /// adjusting its state depending on the match progress
    /// </summary>
    internal class MatchTokenParsingState : IParsingState
    {
        protected string TokenToMatch = string.Empty;

        protected int CurrentCharacterIndex;

        protected IParsingState NextState;
        protected IParsingState ErrorState;

        /// <summary>
        /// Initializes a new instance of the MatchTokenParsingState class 
        /// using the token to match
        /// </summary>
        /// <param name="token">The token to match</param>
        public MatchTokenParsingState(string token)
        {
            ArgumentVerifier.CheckStringArgument(token, "token");

            this.TokenToMatch = token;
        }

        #region IParsingState Members

        /// <summary>
        /// Defines the next state to change the IParsingContext to once the token has been matched
        /// </summary>
        /// <param name="nextState">The state to change to</param>
        public void AddNextState(IParsingState nextState)
        {
            this.NextState = nextState;
        }

        /// <summary>
        /// The state to change the IParsingContext to if an error occurs
        /// </summary>
        /// <param name="errorState">The error state</param>
        public void SetErrorState(IParsingState errorState)
        {
            this.ErrorState = errorState;
        }

        /// <summary>
        /// Processes the character and updates the state depending on it matching the token
        /// </summary>
        /// <param name="context">The parsing context to update state on</param>
        /// <param name="nextChar">The character to process</param>
        /// <returns>The parsing result after processing the character</returns>
        public ParsingResult ProcessCharacter(IParsingContext context, char nextChar)
        {
            ArgumentVerifier.CheckObjectArgument(context, "context");

            return ProcessCharacterImpl(context, nextChar);
        }

        #endregion

        /// <summary>
        /// Processes the character and updates the state depending on it matching the token
        /// </summary>
        /// <param name="context">The parsing context to update state on</param>
        /// <param name="nextChar">The character to process</param>
        /// <returns>The parsing result after processing the character</returns>
        protected virtual ParsingResult ProcessCharacterImpl(IParsingContext context, char nextChar)
        {
            ParsingResult finalResult = ParsingResult.Match;

            System.Diagnostics.Debug.Assert(CurrentCharacterIndex < TokenToMatch.Length);

            if (nextChar == TokenToMatch[CurrentCharacterIndex])
            {
                CurrentCharacterIndex++;

                // only mark the parsing result as done once the whole token has been matched
                if (this.CurrentCharacterIndex == TokenToMatch.Length)
                {
                    if (this.NextState != null)
                    {
                        context.CurrentState = this.NextState;
                    }
                    else
                    {
                        finalResult = ParsingResult.Done;
                    }
                    this.CurrentCharacterIndex = 0;
                }
            }
            else
            {
                if (CurrentCharacterIndex == 0)
                {
                    // character is not start of token
                    finalResult = ParsingResult.Miss;
                }
                else
                {
                    // start of token was matched, but last character didn't
                    context.CurrentState = this.ErrorState;
                    context.Append(this.TokenToMatch.Substring(0, this.CurrentCharacterIndex));
                    finalResult = ParsingResult.Miss;
                }
                this.CurrentCharacterIndex = 0;
            }

            return finalResult;
        }
    }

    /// <summary>
    /// Matches a string token to incoming characters, allowing for tokens escaped by themselves,
    /// adjusting its state depending on the match progress
    /// </summary>
    internal class MatchEscapedTokenParsingState : MatchTokenParsingState
    {
        public MatchEscapedTokenParsingState(string token) : base(token) { }

        protected override ParsingResult ProcessCharacterImpl(IParsingContext context, char nextChar)
        {
            ParsingResult finalResult = ParsingResult.Match;

            System.Diagnostics.Debug.Assert(CurrentCharacterIndex < TokenToMatch.Length * 2);

            // if the token length was 3 this would result in a sequence of 0,1,2,0,1,2,0 etc as currentCharacterIndex increases
            int currentCompareCharacterIndex = CurrentCharacterIndex % TokenToMatch.Length;
            
            if (nextChar == TokenToMatch[currentCompareCharacterIndex])
            {
                CurrentCharacterIndex++;

                // if we have matched the token exactly twice
                if (this.CurrentCharacterIndex == TokenToMatch.Length * 2)
                {
                    if (this.ErrorState != null)
                    {
                        context.CurrentState = this.ErrorState;
                        context.Append(this.TokenToMatch);
                    }
                    else
                    {
                        finalResult = ParsingResult.Done;
                    }
                    this.CurrentCharacterIndex = 0;
                }
            }
            else
            {
                if (CurrentCharacterIndex == 0)
                {
                    finalResult = ParsingResult.Miss;
                }
                else if (currentCompareCharacterIndex == CurrentCharacterIndex)
                {
                    context.CurrentState = this.ErrorState;
                    context.Append(this.TokenToMatch.Substring(0, this.CurrentCharacterIndex));
                    finalResult = ParsingResult.Miss;
                }
                else
                {
                    context.CurrentState = this.NextState;
                    context.Append(this.TokenToMatch.Substring(0, currentCompareCharacterIndex));
                    finalResult = ParsingResult.Miss;
                }

                this.CurrentCharacterIndex = 0;
            }

            return finalResult;
        }
    }
}
